/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package Communication;

import java.io.*;
import java.util.*;
import java.net.*;
import Common.*;
import Response.*;
import Request.ProbeRequest;

//For datagramChannel
import java.nio.channels.DatagramChannel;

/**
 *
 * @author lcy
 */
public class UdpWaitSockThread extends Thread{
    private MulticastSocket _wait_socket;
    private InfoLogger _debugger;
    ByteArrayInputStream _byteStream;
    ObjectInputStream _is;
    byte[] _recvBuf;
    int _buffer_size;
    private DatagramChannel _wait_channel;

    private String _user_name;

    private ObjectOutputStream out_channel;
    private ByteArrayOutputStream _out_buffer;

    public UdpWaitSockThread(){
         _buffer_size = CONSTANTS.UDP_PROBE_BUF_SIZE;
         InitCommComponents();
    }

    public UdpWaitSockThread(int buffer_size){
        _buffer_size = buffer_size;
        InitCommComponents();
    }

    private void InitCommComponents(){
        _debugger = new InfoLogger();
        _user_name = "";
    }

    public void SetRespUserName(String value){
        _user_name = value;
    }

    /*****************************************************
     * Function:OpenUDPServerSocke
     * Summary: Try to open THE udp multicast socket on the client for
     *          any request from other nodes;
     * Input: @port: the port number @ip: the multicast ip address to
     *              which the clietn is bind;
     * Returns: true - open successfully; false - fail to open
     * Side effects: none
     * Pre-Conditions: none
    ******************************************************/
    public boolean OpenUDPWaitSocket(InetAddress ip, int port){

        try{
          
            _wait_socket = new MulticastSocket(port);
            _wait_socket.joinGroup(ip);
            //_wait_socket.setSoTimeout(300);
            _recvBuf = new byte[_buffer_size];
            _byteStream = new ByteArrayInputStream(_recvBuf);

            _out_buffer = new ByteArrayOutputStream(_buffer_size);
            out_channel = new ObjectOutputStream(new BufferedOutputStream(_out_buffer));

        }catch(SocketException sock_ex){
            _debugger.LogInfo("ClientTransceiver: UDP socket cannot be opened on port " + port);
            return false;
        }catch(IOException ioe){
             ioe.printStackTrace();
             _debugger.LogInfo("ClientTransceiver: UDP socket io error at port " + port);
            return false;
        }

        return true;
    }

    @Override
    public void run(){
        while(true){
            try{
                ProbeRequest request;

                while((request = RecvUDPProbe()) != null){
                    //TODO process the msg;
                    InetAddress addr = request.GetLocalInetAddress();
                    int port = request.GetLocalPort();
                    ProbeResponse response = new ProbeResponse();
                    //Debugging
                    System.out.println("Cleint wait sock: request msg: " +
                            (String)request.GetRequestContent());
                    response.SetResultMsg(ResponseMsgList.msg_list[ResponseMsgList.PROBE_RESP_IDX]);
                    response.SetUserName(_user_name);
                    SendUDPResp(response,addr,port);
                }

                CloseWaitSocket();
                break;

            }catch(SocketTimeoutException e){
                // in ms
                try{
                    _debugger.LogInfo("WaitSock: time out");
                    Thread.sleep(10000);
                }catch(InterruptedException ex){
                    //do nothing
                }
            }
        }
        
    }

    //From http://www.javaworld.com/javatips/jw-javatip40.html?page=2
    public ProbeRequest RecvUDPProbe() throws SocketTimeoutException{
        try
        {

          _debugger.LogInfo("Client wait socket: start receiving...");

          DatagramPacket packet = new DatagramPacket(_recvBuf,
                                                     _recvBuf.length);

          //First read to byte array buffer;
          _wait_socket.receive(packet);

          _debugger.LogInfo("Client wait socket: packet received.");

          InetAddress remote_addr = packet.getAddress();
          int port = packet.getPort();

          _is = new ObjectInputStream(new BufferedInputStream(_byteStream));
          ProbeRequest o = (ProbeRequest) _is.readObject();
          _byteStream.reset();

          o.SetLocalInetAddr(remote_addr);
          o.SetLocalPort(port);
          
          return(o);
        }catch(SocketTimeoutException ex){
            throw ex;
        }
        catch (IOException e)
        {
          System.err.println("Exception:  " + e);
          e.printStackTrace();
        }
        catch (ClassNotFoundException e)
        {
          e.printStackTrace();
        }
        
        return(null);

    }

    public boolean SendUDPResp(ProbeResponse response, InetAddress addr, int port){

         try{

            out_channel.flush();
            out_channel.writeObject(response);
            out_channel.flush();

            //Then sendout the datagram packet through the datagram socket;
            //retrieves byte array
            byte[] sendBuf = _out_buffer.toByteArray();
            
            DatagramPacket packet = new DatagramPacket(
                              sendBuf, sendBuf.length,
                              addr,
                              port);

            _wait_socket.send(packet);
            out_channel.reset();


         }catch(IOException ex){
            _debugger.LogInfo("UDP wait socket: io exception");
            return false;
         }

         return true;
    }

    public void CloseWaitSocket(){
        try{

            _wait_socket.close();
            _byteStream.close();
            _is.close();
        }catch(IOException ioe){
            _debugger.LogInfo("UDP wait sock: closing channel error");
        }catch(NullPointerException ex){
            //A null pointer at close is harmless.
            _debugger.LogInfo("UDP wait sock: one of IO channels is null "
                    + "when closing");
        }
    }

}
